CodeMirror.defineMode("plsql", function (config, parserConfig) {
  var indentUnit = config.indentUnit,
    keywords = parserConfig.keywords,
    functions = parserConfig.functions,
    types = parserConfig.types,
    sqlplus = parserConfig.sqlplus,
    multiLineStrings = parserConfig.multiLineStrings;
  var isOperatorChar = /[+\-*&%=<>!?:\/|]/;

  function chain(stream, state, f) {
    state.tokenize = f;
    return f(stream, state);
  }

  var type;

  function ret(tp, style) {
    type = tp;
    return style;
  }

  function tokenBase(stream, state) {
    var ch = stream.next();
    // start of string?
    if (ch == '"' || ch == "'")
      return chain(stream, state, tokenString(ch));
    // is it one of the special signs []{}().,;? Seperator?
    else if (/[\[\]{}\(\),;\.]/.test(ch))
      return ret(ch);
    // start of a number value?
    else if (/\d/.test(ch)) {
      stream.eatWhile(/[\w\.]/);
      return ret("number", "number");
    }
    // multi line comment or simple operator?
    else if (ch == "/") {
      if (stream.eat("*")) {
        return chain(stream, state, tokenComment);
      }
      else {
        stream.eatWhile(isOperatorChar);
        return ret("operator", "operator");
      }
    }
    // single line comment or simple operator?
    else if (ch == "-") {
      if (stream.eat("-")) {
        stream.skipToEnd();
        return ret("comment", "comment");
      }
      else {
        stream.eatWhile(isOperatorChar);
        return ret("operator", "operator");
      }
    }
    // pl/sql variable?
    else if (ch == "@" || ch == "$") {
      stream.eatWhile(/[\w\d\$_]/);
      return ret("word", "variable");
    }
    // is it a operator?
    else if (isOperatorChar.test(ch)) {
      stream.eatWhile(isOperatorChar);
      return ret("operator", "operator");
    }
    else {
      // get the whole word
      stream.eatWhile(/[\w\$_]/);
      // is it one of the listed keywords?
      if (keywords && keywords.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "keyword");
      // is it one of the listed functions?
      if (functions && functions.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "builtin");
      // is it one of the listed types?
      if (types && types.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "variable-2");
      // is it one of the listed sqlplus keywords?
      if (sqlplus && sqlplus.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "variable-3");
      // default: just a "word"
      return ret("word", "plsql-word");
    }
  }

  function tokenString(quote) {
    return function (stream, state) {
      var escaped = false, next, end = false;
      while ((next = stream.next()) != null) {
        if (next == quote && !escaped) {
          end = true;
          break;
        }
        escaped = !escaped && next == "\\";
      }
      if (end || !(escaped || multiLineStrings))
        state.tokenize = tokenBase;
      return ret("string", "plsql-string");
    };
  }

  function tokenComment(stream, state) {
    var maybeEnd = false, ch;
    while (ch = stream.next()) {
      if (ch == "/" && maybeEnd) {
        state.tokenize = tokenBase;
        break;
      }
      maybeEnd = (ch == "*");
    }
    return ret("comment", "plsql-comment");
  }

  // Interface

  return {
    startState:function (basecolumn) {
      return {
        tokenize:tokenBase,
        startOfLine:true
      };
    },

    token:function (stream, state) {
      if (stream.eatSpace()) return null;
      var style = state.tokenize(stream, state);
      return style;
    }
  };
});

(function () {
  function keywords(str) {
    var obj = {}, words = str.split(" ");
    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
    return obj;
  }

  var cKeywords = "abort accept access add all alter and any array arraylen as asc assert assign at attributes audit " +
    "authorization avg " +
    "base_table begin between binary_integer body boolean by " +
    "case cast char char_base check close cluster clusters colauth column comment commit compress connect " +
    "connected constant constraint crash create current currval cursor " +
    "data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete " +
    "desc digits dispose distinct do drop " +
    "else elsif enable end entry escape exception exception_init exchange exclusive exists exit external " +
    "fast fetch file for force form from function " +
    "generic goto grant group " +
    "having " +
    "identified if immediate in increment index indexes indicator initial initrans insert interface intersect " +
    "into is " +
    "key " +
    "level library like limited local lock log logging long loop " +
    "master maxextents maxtrans member minextents minus mislabel mode modify multiset " +
    "new next no noaudit nocompress nologging noparallel not nowait number_base " +
    "object of off offline on online only open option or order out " +
    "package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior " +
    "private privileges procedure public " +
    "raise range raw read rebuild record ref references refresh release rename replace resource restrict return " +
    "returning reverse revoke rollback row rowid rowlabel rownum rows run " +
    "savepoint schema segment select separate session set share snapshot some space split sql start statement " +
    "storage subtype successful synonym " +
    "tabauth table tables tablespace task terminate then to trigger truncate type " +
    "union unique unlimited unrecoverable unusable update use using " +
    "validate value values variable view views " +
    "when whenever where while with work";

  var cFunctions = "abs acos add_months ascii asin atan atan2 average " +
    "bfilename " +
    "ceil chartorowid chr concat convert cos cosh count " +
    "decode deref dual dump dup_val_on_index " +
    "empty error exp " +
    "false floor found " +
    "glb greatest " +
    "hextoraw " +
    "initcap instr instrb isopen " +
    "last_day least lenght lenghtb ln lower lpad ltrim lub " +
    "make_ref max min mod months_between " +
    "new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower " +
    "nls_sort nls_upper nlssort no_data_found notfound null nvl " +
    "others " +
    "power " +
    "rawtohex reftohex round rowcount rowidtochar rpad rtrim " +
    "sign sin sinh soundex sqlcode sqlerrm sqrt stddev substr substrb sum sysdate " +
    "tan tanh to_char to_date to_label to_multi_byte to_number to_single_byte translate true trunc " +
    "uid upper user userenv " +
    "variance vsize";

  var cTypes = "bfile blob " +
    "character clob " +
    "dec " +
    "float " +
    "int integer " +
    "mlslabel " +
    "natural naturaln nchar nclob number numeric nvarchar2 " +
    "real rowtype " +
    "signtype smallint string " +
    "varchar varchar2";

  var cSqlplus = "appinfo arraysize autocommit autoprint autorecovery autotrace " +
    "blockterminator break btitle " +
    "cmdsep colsep compatibility compute concat copycommit copytypecheck " +
    "define describe " +
    "echo editfile embedded escape exec execute " +
    "feedback flagger flush " +
    "heading headsep " +
    "instance " +
    "linesize lno loboffset logsource long longchunksize " +
    "markup " +
    "native newpage numformat numwidth " +
    "pagesize pause pno " +
    "recsep recsepchar release repfooter repheader " +
    "serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber " +
    "sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix " +
    "tab term termout time timing trimout trimspool ttitle " +
    "underline " +
    "verify version " +
    "wrap";

  CodeMirror.defineMIME("text/x-plsql", {
    name:"plsql",
    keywords:keywords(cKeywords),
    functions:keywords(cFunctions),
    types:keywords(cTypes),
    sqlplus:keywords(cSqlplus)
  });
}());
